LÀr dig optimera React custom hooks genom att förstÄ och hantera beroenden i useEffect. FörbÀttra prestanda och undvik vanliga fallgropar.
React Custom Hooks-beroenden: BemÀstra effektoptimering för prestanda
React custom hooks Àr ett kraftfullt verktyg för att abstrahera och ÄteranvÀnda logik i dina komponenter. Felaktig hantering av beroenden inom useEffect kan dock leda till prestandaproblem, onödiga ominlÀsningar och till och med oÀndliga loopar. Den hÀr guiden ger en omfattande förstÄelse för useEffect-beroenden och bÀsta praxis för att optimera dina custom hooks.
FörstÄ useEffect och beroenden
useEffect-hooken i React lÄter dig utföra sidoeffekter i dina komponenter, som att hÀmta data, manipulera DOM eller sÀtta upp prenumerationer. Det andra argumentet till useEffect Àr en valfri array med beroenden. Denna array talar om för React nÀr effekten ska köras igen. Om nÄgot av vÀrdena i beroende-arrayen Àndras mellan renderingar, kommer effekten att köras om. Om beroende-arrayen Àr tom ([]), körs effekten endast en gÄng efter den initiala renderingen. Om beroende-arrayen utelÀmnas helt, körs effekten efter varje rendering.
Varför beroenden spelar roll
Beroenden Àr avgörande för att kontrollera nÀr din effekt körs. Om du inkluderar ett beroende som faktiskt inte behöver utlösa effekten, kommer du att fÄ onödiga omkörningar, vilket potentiellt kan pÄverka prestandan. OmvÀnt, om du utelÀmnar ett beroende som *behöver* utlösa effekten, kanske din komponent inte uppdateras korrekt, vilket leder till buggar och ovÀntat beteende. LÄt oss titta pÄ ett grundlÀggande exempel:
import React, { useState, useEffect } from 'react';
function ExampleComponent({ userId }) {
const [userData, setUserData] = useState(null);
useEffect(() => {
async function fetchData() {
const response = await fetch(`https://api.example.com/users/${userId}`);
const data = await response.json();
setUserData(data);
}
fetchData();
}, [userId]); // Beroende-array: kör endast om nÀr userId Àndras
if (!userData) {
return Laddar...
;
}
return (
{userData.name}
{userData.email}
);
}
export default ExampleComponent;
I det hÀr exemplet hÀmtar effekten anvÀndardata frÄn ett API. Beroende-arrayen inkluderar userId. Detta sÀkerstÀller att effekten endast körs nÀr userId-propen Àndras. Om userId förblir densamma körs inte effekten om, vilket förhindrar onödiga API-anrop.
Vanliga fallgropar och hur man undviker dem
Flera vanliga fallgropar kan uppstÄ nÀr man arbetar med useEffect-beroenden. Att förstÄ dessa fallgropar och hur man undviker dem Àr avgörande för att skriva effektiv och felfri React-kod.
1. Saknade beroenden
Det vanligaste misstaget Àr att utelÀmna ett beroende som *bör* inkluderas i beroende-arrayen. Detta kan leda till gamla stÀngningar och ovÀntat beteende. Till exempel:
import React, { useState, useEffect } from 'react';
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const intervalId = setInterval(() => {
setCount(count + 1); // Potentiellt problem: `count` Àr inte ett beroende
}, 1000);
return () => clearInterval(intervalId);
}, []); // Tom beroende-array: effekten körs bara en gÄng
return RĂ€kning: {count}
;
}
export default Counter;
I det hÀr exemplet inkluderas inte count-variabeln i beroende-arrayen. Som ett resultat anvÀnder setInterval-callbacken alltid det initiala vÀrdet av count (som Àr 0). RÀknaren kommer inte att öka korrekt. Den korrekta versionen bör inkludera count i beroende-arrayen:
import React, { useState, useEffect } from 'react';
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const intervalId = setInterval(() => {
setCount(prevCount => prevCount + 1); // Korrekt: anvÀnd funktionell uppdatering
}, 1000);
return () => clearInterval(intervalId);
}, []); // Nu behövs inget beroende eftersom vi anvÀnder den funktionella uppdateringsformen.
return RĂ€kning: {count}
;
}
export default Counter;
LÀrdom: Se alltid till att alla variabler som anvÀnds inuti effekten och som definieras utanför effekten omfattas av beroende-arrayen. Om möjligt, anvÀnd funktionella uppdateringar (setCount(prevCount => prevCount + 1)) för att undvika att behöva count-beroendet.
2. Inkludera onödiga beroenden
Att inkludera onödiga beroenden kan leda till överdrivna ominlÀsningar och prestandaförsÀmring. TÀnk dig till exempel en komponent som tar emot en prop som Àr ett objekt:
import React, { useState, useEffect } from 'react';
function DisplayData({ data }) {
const [processedData, setProcessedData] = useState(null);
useEffect(() => {
// Utför nÄgon komplex databehandling
const result = processData(data);
setProcessedData(result);
}, [data]); // Problem: `data` Àr ett objekt, sÄ det Àndras vid varje rendering
function processData(data) {
// Komplex databehandlingslogik
return data;
}
if (!processedData) {
return Laddar...
;
}
return {processedData.value}
;
}
export default DisplayData;
I det hÀr fallet, Àven om innehÄllet i data-objektet förblir logiskt detsamma, skapas ett nytt objekt vid varje rendering av förÀldrakomponenten. Det betyder att useEffect kommer att köras om vid varje rendering, Àven om databehandlingen faktiskt inte behöver göras om. HÀr Àr nÄgra strategier för att lösa detta:
Lösning 1: Memoizering med useMemo
AnvÀnd useMemo för att memoizera data-propen. Detta kommer bara att Äterskapa data-objektet om dess relevanta egenskaper Àndras.
import React, { useState, useEffect, useMemo } from 'react';
function ParentComponent() {
const [value, setValue] = useState(0);
// Memoizera `data`-objektet
const data = useMemo(() => ({ value }), [value]);
return ;
}
function DisplayData({ data }) {
const [processedData, setProcessedData] = useState(null);
useEffect(() => {
// Utför nÄgon komplex databehandling
const result = processData(data);
setProcessedData(result);
}, [data]); // Nu Àndras `data` bara nÀr `value` Àndras
function processData(data) {
// Komplex databehandlingslogik
return data;
}
if (!processedData) {
return Laddar...
;
}
return {processedData.value}
;
}
export default ParentComponent;
Lösning 2: Destrukturering av prop
Skicka enskilda egenskaper av data-objektet som props istÀllet för hela objektet. Detta gör att useEffect bara körs om nÀr de specifika egenskaperna den Àr beroende av Àndras.
import React, { useState, useEffect } from 'react';
function ParentComponent() {
const [value, setValue] = useState(0);
return ;
}
function DisplayData({ value }) {
const [processedData, setProcessedData] = useState(null);
useEffect(() => {
// Utför nÄgon komplex databehandling
const result = processData(value);
setProcessedData(result);
}, [value]); // Kör endast om nÀr `value` Àndras
function processData(value) {
// Komplex databehandlingslogik
return { value }; // Linda in i objekt om det behövs inuti DisplayData
}
if (!processedData) {
return Laddar...
;
}
return {processedData.value}
;
}
export default ParentComponent;
Lösning 3: AnvÀnda useRef för att jÀmföra vÀrden
Om du behöver jÀmföra *innehÄllet* i data-objektet och endast köra effekten igen nÀr innehÄllet Àndras, kan du anvÀnda useRef för att lagra det tidigare vÀrdet av data och utföra en djup jÀmförelse.
import React, { useState, useEffect, useRef } from 'react';
import { isEqual } from 'lodash'; // KrÀver lodash-bibliotek (npm install lodash)
function DisplayData({ data }) {
const [processedData, setProcessedData] = useState(null);
const previousData = useRef(data);
useEffect(() => {
if (!isEqual(data, previousData.current)) {
// Utför nÄgon komplex databehandling
const result = processData(data);
setProcessedData(result);
previousData.current = data;
}
}, [data]); // `data` Àr fortfarande i beroende-arrayen, men vi kontrollerar för djup likhet
function processData(data) {
// Komplex databehandlingslogik
return data;
}
if (!processedData) {
return Laddar...
;
}
return {processedData.value}
;
}
export default DisplayData;
Notera: Djupa jÀmförelser kan vara dyra, sÄ anvÀnd detta tillvÀgagÄngssÀtt med omdöme. Dessutom bygger detta exempel pÄ lodash-biblioteket. Du kan installera det med npm install lodash eller yarn add lodash.
LĂ€rdom: ĂvervĂ€g noggrant vilka beroenden som verkligen Ă€r nödvĂ€ndiga. Undvik att inkludera objekt eller arrayer som Ă„terskapas vid varje rendering om deras innehĂ„ll förblir logiskt detsamma. AnvĂ€nd memoizering, destrukturering eller djupa jĂ€mförelse-tekniker för att optimera prestandan.
3. OĂ€ndliga loopar
Felaktig hantering av beroenden kan leda till oÀndliga loopar, dÀr useEffect-hooken kontinuerligt körs om, vilket fÄr din komponent att frysa eller krascha. Detta hÀnder ofta nÀr effekten uppdaterar en tillstÄndsvariabel som ocksÄ Àr ett beroende för effekten. Till exempel:
import React, { useState, useEffect } from 'react';
function InfiniteLoop() {
const [data, setData] = useState(null);
useEffect(() => {
// HÀmta data frÄn ett API
fetch('https://api.example.com/data')
.then(response => response.json())
.then(result => {
setData(result); // Uppdaterar `data`-tillstÄndet
});
}, [data]); // Problem: `data` Àr ett beroende, sÄ effekten körs om nÀr `data` Àndras
if (!data) {
return Laddar...
;
}
return {data.value}
;
}
export default InfiniteLoop;
I det hÀr exemplet hÀmtar effekten data och sÀtter den till data-tillstÄndsvariabeln. data Àr dock ocksÄ ett beroende för effekten. Detta innebÀr att varje gÄng data uppdateras, körs effekten om, hÀmtar data igen och sÀtter data igen, vilket leder till en oÀndlig loop. Det finns flera sÀtt att lösa detta:
Lösning 1: Tom beroende-array (endast initial laddning)
Om du bara vill hÀmta data en gÄng nÀr komponenten monteras, kan du anvÀnda en tom beroende-array:
import React, { useState, useEffect } from 'react';
function InfiniteLoop() {
const [data, setData] = useState(null);
useEffect(() => {
// HÀmta data frÄn ett API
fetch('https://api.example.com/data')
.then(response => response.json())
.then(result => {
setData(result);
});
}, []); // Tom beroende-array: effekten körs bara en gÄng
if (!data) {
return Laddar...
;
}
return {data.value}
;
}
export default InfiniteLoop;
Lösning 2: AnvÀnd en separat tillstÄnd för laddning
AnvÀnd en separat tillstÄndsvariabel för att spÄra om data har laddats. Detta förhindrar att effekten körs om nÀr data-tillstÄndet Àndras.
import React, { useState, useEffect } from 'react';
function InfiniteLoop() {
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
if (isLoading) {
// HÀmta data frÄn ett API
fetch('https://api.example.com/data')
.then(response => response.json())
.then(result => {
setData(result);
setIsLoading(false);
});
}
}, [isLoading]); // Kör endast om nÀr `isLoading` Àndras
if (!data) {
return Laddar...
;
}
return {data.value}
;
}
export default InfiniteLoop;
Lösning 3: Villkorlig datahÀmtning
HÀmta data endast om den för nÀrvarande Àr null. Detta förhindrar efterföljande hÀmtningar efter att den initiala datan har laddats.
import React, { useState, useEffect } from 'react';
function InfiniteLoop() {
const [data, setData] = useState(null);
useEffect(() => {
if (!data) {
// HÀmta data frÄn ett API
fetch('https://api.example.com/data')
.then(response => response.json())
.then(result => {
setData(result);
});
}
}, [data]); // `data` Àr fortfarande ett beroende men effekten Àr villkorlig
if (!data) {
return Laddar...
;
}
return {data.value}
;
}
export default InfiniteLoop;
LÀrdom: Var extremt försiktig nÀr du uppdaterar en tillstÄndsvariabel som ocksÄ Àr ett beroende för effekten. AnvÀnd tomma beroende-arrayer, separata laddningstillstÄnd eller villkorlig logik för att förhindra oÀndliga loopar.
4. Mutera objekt och arrayer
NÀr du arbetar med muterbara objekt eller arrayer som beroenden, kommer Àndringar i objektets egenskaper eller arrayelement inte automatiskt att utlösa effekten. Detta beror pÄ att React utför en grundlÀggande jÀmförelse av beroendena.
import React, { useState, useEffect } from 'react';
function MutableObject() {
const [config, setConfig] = useState({ theme: 'light', language: 'en' });
useEffect(() => {
console.log('Config Àndrad:', config);
}, [config]); // Problem: Ăndringar i `config.theme` eller `config.language` utlöser inte effekten
const toggleTheme = () => {
// Mutera objektet
config.theme = config.theme === 'light' ? 'dark' : 'light';
setConfig(config); // Detta kommer inte att utlösa en ominlÀsning eller effekten
};
return (
Tema: {config.theme}, SprÄk: {config.language}
);
}
export default MutableObject;
I det hÀr exemplet modifierar toggleTheme-funktionen direkt config-objektet, vilket Àr dÄlig praxis. Reacts grundlÀggande jÀmförelse ser att config fortfarande Àr *samma* objekt i minnet, Àven om dess egenskaper har Àndrats. För att fixa detta mÄste du skapa ett *nytt* objekt nÀr du uppdaterar tillstÄndet:
import React, { useState, useEffect } from 'react';
function MutableObject() {
const [config, setConfig] = useState({ theme: 'light', language: 'en' });
useEffect(() => {
console.log('Config Àndrad:', config);
}, [config]); // Nu utlöses effekten nÀr `config` Àndras
const toggleTheme = () => {
setConfig({ ...config, theme: config.theme === 'light' ? 'dark' : 'light' }); // Skapa ett nytt objekt
};
return (
Tema: {config.theme}, SprÄk: {config.language}
);
}
export default MutableObject;
Genom att anvÀnda spread-operatorn (...config) skapar vi ett nytt objekt med den uppdaterade theme-egenskapen. Detta utlöser en ominlÀsning och effekten körs om.
LÀrdom: Behandla alltid tillstÄndsvariabler som oförÀnderliga. NÀr du uppdaterar objekt eller arrayer, skapa nya instanser istÀllet för att modifiera befintliga. AnvÀnd spread-operatorn (...), Array.map(), Array.filter() eller liknande tekniker för att skapa nya kopior.
Optimera Custom Hooks med Beroenden
Nu nÀr vi förstÄr de vanliga fallgroparna, lÄt oss titta pÄ hur man optimerar custom hooks genom att noggrant hantera beroenden.
1. Memoizera funktioner med useCallback
Om din custom hook returnerar en funktion som anvÀnds som ett beroende i en annan useEffect, bör du memoizera funktionen med useCallback. Detta förhindrar att funktionen Äterskapas vid varje rendering, vilket onödigt skulle utlösa effekten.
import React, { useState, useEffect, useCallback } from 'react';
function useFetchData(url) {
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
const fetchData = useCallback(async () => {
setIsLoading(true);
try {
const response = await fetch(url);
const result = await response.json();
setData(result);
} catch (err) {
setError(err);
} finally {
setIsLoading(false);
}
}, [url]); // Memoizera `fetchData` baserat pÄ `url`
useEffect(() => {
fetchData();
}, [fetchData]); // Nu Àndras `fetchData` bara nÀr `url` Àndras
return { data, isLoading, error };
}
function MyComponent() {
const [userId, setUserId] = useState(1);
const { data, isLoading, error } = useFetchData(`https://api.example.com/users/${userId}`);
return (
{/* ... */}
);
}
export default MyComponent;
I det hÀr exemplet memoizeras fetchData-funktionen med useCallback. Beroende-arrayen inkluderar url, som Àr den enda variabeln som pÄverkar funktionens beteende. Detta sÀkerstÀller att fetchData bara Àndras nÀr url Àndras. DÀrför kommer useEffect-hooken i useFetchData bara att köras om nÀr url Àndras.
2. AnvÀnd useRef för stabila referenser
Ibland behöver du komma Ät det senaste vÀrdet av en prop eller tillstÄndsvariabel inuti en effekt, men du vill inte att effekten ska köras om nÀr det vÀrdet Àndras. I det hÀr fallet kan du anvÀnda useRef för att skapa en stabil referens till vÀrdet.
import React, { useState, useEffect, useRef } from 'react';
function LogLatestValue({ value }) {
const latestValue = useRef(value);
useEffect(() => {
latestValue.current = value; // Uppdatera referensen vid varje rendering
}, [value]); // Uppdatera referensen nÀr `value` Àndras
useEffect(() => {
// Logga det senaste vÀrdet efter 5 sekunder
const timerId = setTimeout(() => {
console.log('Senaste vĂ€rdet:', latestValue.current); // Ă
tkomst till senaste vÀrdet frÄn referensen
}, 5000);
return () => clearTimeout(timerId);
}, []); // Effekten körs bara en gÄng vid montering
return VĂ€rde: {value}
;
}
export default LogLatestValue;
I det hÀr exemplet uppdateras latestValue-referensen vid varje rendering med det aktuella vÀrdet av value-propen. Men effekten som loggar vÀrdet körs bara en gÄng vid montering, tack vare den tomma beroende-arrayen. Inuti effekten kommer vi Ät det senaste vÀrdet med latestValue.current. Detta gör att vi kan komma Ät det mest uppdaterade vÀrdet av value utan att orsaka att effekten körs om varje gÄng value Àndras.
3. Skapa anpassad abstraktion
Skapa en anpassad jÀmförelsefunktion eller abstraktionslager om du arbetar med ett objekt, och endast en liten delmÀngd av dess egenskaper Àr viktig för useEffect-anropen.
import React, { useState, useEffect } from 'react';
// Anpassad jÀmförelsefunktion för att endast spÄra temaÀndringar.
function useTheme(config) {
const [theme, setTheme] = useState(config.theme);
useEffect(() => {
setTheme(config.theme);
}, [config.theme]);
return theme;
}
function ConfigComponent({ config }) {
const theme = useTheme(config);
return (
Det aktuella temat Àr {theme}
)
}
export default ConfigComponent;
LÀrdom: AnvÀnd useCallback för att memoizera funktioner som anvÀnds som beroenden. AnvÀnd useRef för att skapa stabila referenser till vÀrden som du behöver komma Ät inuti effekter utan att orsaka att effekterna körs om. NÀr du hanterar komplexa objekt eller arrayer, övervÀg att skapa anpassade jÀmförelsefunktioner eller abstraktionslager för att endast utlösa effekter nÀr relevanta egenskaper Àndras.
Globala övervÀganden
NÀr du utvecklar React-applikationer för en global publik Àr det viktigt att tÀnka pÄ hur beroenden kan pÄverka lokalisering och internationalisering. HÀr Àr nÄgra viktiga övervÀganden:
1. LokalÀndringar
Om din komponent Àr beroende av anvÀndarens lokalisering (t.ex. för att formatera datum, siffror eller valutor), bör du inkludera lokaliseringen i beroende-arrayen. Detta sÀkerstÀller att effekten körs om nÀr lokaliseringen Àndras och uppdaterar komponenten med korrekt formatering.
import React, { useState, useEffect } from 'react';
import { format } from 'date-fns'; // KrÀver date-fns-bibliotek (npm install date-fns)
function LocalizedDate({ date, locale }) {
const [formattedDate, setFormattedDate] = useState('');
useEffect(() => {
setFormattedDate(format(date, 'PPPP', { locale }));
}, [date, locale]); // Kör om nÀr `date` eller `locale` Àndras
return {formattedDate}
;
}
export default LocalizedDate;
I det hÀr exemplet anvÀnds format-funktionen frÄn date-fns-biblioteket för att formatera datumet enligt den angivna lokaliseringen. locale inkluderas i beroende-arrayen, sÄ effekten körs om nÀr lokaliseringen Àndras och uppdaterar det formaterade datumet.
2. TidszonsövervÀganden
NÀr du arbetar med datum och tider, var medveten om tidszoner. Om din komponent visar datum eller tider i anvÀndarens lokala tidszon kan du behöva inkludera tidszonen i beroende-arrayen. TidszonsÀndringar Àr dock mindre frekventa Àn lokalÀndringar, sÄ du kan övervÀga att anvÀnda en separat mekanism för att uppdatera tidszonen, till exempel en global kontext.
3. Valutformatering
NÀr du formaterar valutor, anvÀnd korrekt valutakod och lokalisering. Inkludera bÄde i beroende-arrayen för att sÀkerstÀlla att valutan formateras korrekt för anvÀndarens region.
import React, { useState, useEffect } from 'react';
function LocalizedCurrency({ amount, currency, locale }) {
const [formattedCurrency, setFormattedCurrency] = useState('');
useEffect(() => {
setFormattedCurrency(new Intl.NumberFormat(locale, { style: 'currency', currency }).format(amount));
}, [amount, currency, locale]); // Kör om nÀr `amount`, `currency` eller `locale` Àndras
return {formattedCurrency}
;
}
export default LocalizedCurrency;
LÀrdom: NÀr du utvecklar för en global publik, tÀnk alltid pÄ hur beroenden kan pÄverka lokalisering och internationalisering. Inkludera lokalisering, tidszon och valutakod i beroende-arrayen nÀr det Àr nödvÀndigt för att sÀkerstÀlla att dina komponenter visar data korrekt för anvÀndare i olika regioner.
Slutsats
Att bemÀstra useEffect-beroenden Àr avgörande för att skriva effektiva, felfria och högpresterande React custom hooks. Genom att förstÄ de vanliga fallgroparna och tillÀmpa optimeringsteknikerna som diskuteras i den hÀr guiden kan du skapa custom hooks som Àr bÄde ÄteranvÀndbara och underhÄllbara. Kom ihÄg att noggrant övervÀga vilka beroenden som verkligen Àr nödvÀndiga, anvÀnda memoizering och stabila referenser nÀr det Àr lÀmpligt, och vara medveten om globala övervÀganden som lokalisering och internationalisering. Genom att följa dessa bÀsta praxis kan du frigöra den fulla potentialen hos React custom hooks och bygga högkvalitativa applikationer för en global publik.
Den hÀr omfattande guiden har tÀckt mycket. För att sammanfatta, hÀr Àr de viktigaste lÀrdomarna:
- FörstÄ syftet med beroenden: De kontrollerar nÀr din effekt körs.
- Undvik saknade beroenden: Se till att alla variabler som anvÀnds inuti effekten inkluderas.
- Eliminera onödiga beroenden: AnvÀnd memoizering, destrukturering eller djup jÀmförelse.
- Förhindra oÀndliga loopar: Var försiktig nÀr du uppdaterar tillstÄndsvariabler som ocksÄ Àr beroenden.
- Behandla tillstÄnd som oförÀnderligt: Skapa nya objekt eller arrayer vid uppdatering.
- Memoizera funktioner med
useCallback: Förhindra onödiga ominlÀsningar. - AnvÀnd
useRefför stabila referenser: Ă tkomst till senaste vĂ€rdet utan att utlösa ominlĂ€sningar. - ĂvervĂ€g globala implikationer: Ta hĂ€nsyn till Ă€ndringar i lokalisering, tidszon och valuta.
Genom att tillÀmpa dessa principer kan du skriva mer robusta och effektiva React custom hooks som förbÀttrar prestandan och underhÄllbarheten i dina applikationer.